--#NODOC_DEFAULT
--i named this Core because it is the core of the addon, everything comes back to it, if you dislike the naming scheme: kill yourself
--thanks
local VisualThemes = VisualThemes
VisualThemes.name = "VisualThemes"
local AceOO = AceLibrary("AceOO-2.0")

local Waterfall = AceLibrary("Waterfall-1.0")

local L = VisualThemes:GetLocaleTable()

do
	local rev = tonumber(("$Revision: 56467 $"):match("%d+"))
	VisualThemes.version = "r" .. rev
	VisualThemes.revision = rev
end

VisualThemes.hasIcon = "Interface\\Icons\\INV_Misc_Gem_01"
VisualThemes.blizzardTooltip = true
VisualThemes.hideWithoutStandby = true
VisualThemes.hasNoColor = true
VisualThemes.independentProfile = true


VisualThemes:RegisterDB("VisualThemesDB")
VisualThemes:RegisterDefaults("profile", {OldGroups = {}, Groups = {},})

local IsEnabled, IsInitialized = false, false

function VisualThemes:IsEnabled()
	return IsEnabled
end

function VisualThemes:IsDisabled()
	return not IsEnabled
end

local addOnValidation = {L["Default"], L["Disabled"]}
local defaultThemes = {}
local options = {
	--name = "\232\167\134\232\167\137\228\184\187\233\162\152",
	desc = "VisualThemes Base that provides animated transitions to UI elements.",
	icon = [[Interface\Icons\INV_Misc_Gem_01]],
	handler = VisualThemes,
	type = "group",
	args = {
		ThemeOptions = {
			type = "group",
			name = L["Theme Options"],
			desc = L["Configure Theme options."],
			args = {},
			order = 1,
		},
		GroupOptions = {
			type = "group",
			name = L["Frame Group Options"],
			desc = L["Each frame belongs to a grouping and each group can have its own independent theme."],
			args = {},
			order = 2,
		},
		Blank = {type="header", order = 3},
		DefaultTheme = {
			type = "text",
			name = L["Default Theme"],
			desc = L["Select the default theme"],
			get = "GetDefaultTheme",
			set = "SetDefaultTheme",
			validate = defaultThemes,
			order = 4,
		},
	},
}


local function GetGroupOption(group)
	return VisualThemes.db.profile.Groups[group]
end
local function SetGroupOption(group, value)
	VisualThemes.db.profile.Groups[group] = value
	VisualThemes:UpdateGroupTheme(group)
end
function VisualThemes:UpdateGroupOptions()
	local groupOptions = options.args.GroupOptions.args
	local groups = self.db.profile.Groups
	for group, setting in pairs(groups) do
		if not groupOptions[group] then
			groupOptions[group] = {
				type = "text",
				name = group,
				desc = L["Configures which theme a frame grouping should use, if any."],
				get = GetGroupOption,
				set = SetGroupOption,
				validate = addOnValidation,
				passValue = group,
			}
		end
	end
end

local MergeRegisteredFrames
function VisualThemes:OnInitialize()
	IsInitialized = true
	MergeRegisteredFrames(self)
	MergeRegisteredFrames = nil
	self:RegisterChatCommand('/VisualThemes', '/VT', {
		type = 'execute',
		func = function()
			Waterfall:Open("VisualThemes")
		end
	})
	 --since i'm not using the options table for the slash command the options table never gets "injected"
	AceLibrary("AceConsole-2.0"):InjectAceOptionsTable(self, options)

	for name, theme in self:IterateThemes() do
		theme:OnInitialize()
	end
end

VisualThemes.OnMenuRequest = options

Waterfall:Register("VisualThemes", "aceOptions", options, "title", L["VisualThemes Configuration"], "treeLevels", 5)


function VisualThemes:OnEnable(first)
	IsEnabled = true

	self:RegisterEvent("ADDON_LOADED")

	for name, theme in self:IterateThemes() do
		theme:OnEnable(first)
	end

	self:ThemeRegisteredFrames()
	self:UpdateGroupOptions()


	self:UpdateOptionsTable()
end

function VisualThemes:UpdateOptionsTable()
	local themeOptions = options.args.ThemeOptions
	for name, theme in self:IterateThemes() do
		themeOptions.args[theme.Name] = theme.options
	end
end

function VisualThemes:OnClick()
	Waterfall:Open("VisualThemes")
end

do
	local classes = {}
	function VisualThemes:RegisterClass(className, class)
		classes[className] = class
	end

	function VisualThemes:New(className, ...)
		if classes[className] then
			return classes[className]:new(...)
		end
	end
	getmetatable(VisualThemes).__call = VisualThemes.New
end

local ITheme
function VisualThemes:RegisterInterface(interfaceName, interface)
	--check for certain Interfaces important to the core
	if interfaceName == "ITheme" then
		ITheme = interface
	end
end

do
	local defaultTheme
	function VisualThemes:GetDefaultTheme()
		return defaultTheme
	end
	function VisualThemes:HasDefaultTheme()
		return defaultTheme ~= nil
	end
	function VisualThemes:SetDefaultTheme(theme)
		if type(theme) == "string" and self:HasTheme(theme) then
			if defaultTheme ~= theme then
				defaultTheme = theme
				self:DefaultThemeChanged()
			end
		elseif AceOO.inherits(theme, ITheme) and self:HasTheme(theme.Name) then
			if defaultTheme ~= theme.Name then
				defaultTheme = theme.Name
				self:DefaultThemeChanged()
			end
		end
	end
end

do
	local groups, frames, depths, oldGroups = {}, {}, {}
	MergeRegisteredFrames = function(self)
		oldGroups = self.db.profile.OldGroups
		local orig = self.db.profile.Groups
		self.db.profile.Groups = groups
		for group in pairs(orig) do
			if groups[group] then
				groups[group] = self:HasTheme(orig[group]) and orig[group] or L["Default"]
			else
				oldGroups[group] = self:HasTheme(orig[group]) and orig[group] or nil
			end
		end
	end
	function VisualThemes:DefaultThemeChanged()
		if IsInitialized then
			for group, theme in pairs(groups) do
				if theme == L["Default"] then
					for frame, fGroup in pairs(frames) do
						if group == fGroup then
							if self:IsThemed(frame) then
								self:Detheme(frame, depths[frame])
							end
							self:Theme(frame, self:GetTheme(self:GetDefaultTheme()), depths[frame])
						end
					end
				end
			end
		end
	end
	--[[#EXPLICIT_DOC
	Arguments:
		string - The name of the grouping the frames fall under, such as "Character Frames" or "MyAddon Frames"
		[optional] number - The depth of children that should also be themed, default is 0
		strings|frame objects - The global frame name or reference to a frame object to add to the grouping

	Notes:
		*Use global names if possible.
		*Calling :RegisterFrames again with the same group adds the frame(s) to that group.

	Example:
		local f = CreateFrame("Frame", "MyAddon_Frame", parent)
		if VisualThemes then
			VisualThemes:RegisterFrames("MyAddon Frames", 0, "MyAddon_Frame")
		end
	]]
	function VisualThemes:RegisterFrames(group, depth, ...)
		if not groups[group] then
			groups[group] = oldGroups and oldGroups[group] or L["Default"]
		end
		for i=1, select("#", ...) do
			local frame = select(i, ...)
			frames[frame] = group
			if type(depth) == "number" and depth > 0 then
				depths[frame] = depth
			end
			if IsEnabled then
				if not self:IsGroupDisabled(group) then
					if self:IsThemed(frame) then
						self:Detheme(frame, depths[frame])
					end
					self:Theme(frame, groups[group], depth)
				end
			end
		end
		if IsInitialized then
			self:UpdateGroupOptions()

		end
	end
	function VisualThemes:ThemeRegisteredFrames()
		for frame, group in pairs(frames) do
			local theme = group[group]
			if self:IsGroupDisabled(group) then
				if self:IsThemed(frame) then
					self:Detheme(frame, depths[frame])
				end
			else
				if self:IsThemed(frame) then
					self:Detheme(frame, depths[frame])
				end
				self:Theme(frame, theme, depths[frame])
			end
		end
	end
	function VisualThemes:UpdateGroupTheme(group)
		for frame, fGroup in pairs(frames) do
			if fGroup == group then
				local theme = group[group]
				if self:IsGroupDisabled(group) then
					if self:IsThemed(frame) then
						self:Detheme(frame, depths[frame])
					end
				else
					if self:IsThemed(frame) then
						self:Detheme(frame, depths[frame])
					end
					self:Theme(frame, theme, depths[frame])
				end
			end
		end
	end


	function VisualThemes:IsGroupDisabled(group)
		return groups[group] == nil or groups[group] == L["Disabled"]
	end
	function VisualThemes:GetGroupTheme(group)
		return groups[group]
	end
end

function VisualThemes:IsAddOnLoadable(addon)
	return select(5, GetAddOnInfo(addon)) ~= nil
end

do
	local themes = {}
	function VisualThemes:RegisterTheme(themeName, theme)
		if AceOO.inherits(theme, ITheme) and not themes[themeName] then
			themes[themeName] = theme
			addOnValidation[#addOnValidation + 1] = themeName
			defaultThemes[#defaultThemes + 1] = themeName
			if not self:HasDefaultTheme() then
				self:SetDefaultTheme(themeName)
			end
			if IsInitialized then
				theme:OnInitialize()
			end
			if IsEnabled then
				theme:OnEnable(true)
				self:UpdateOptionsTable()
			end

		end
	end
	function VisualThemes:IterateThemes()
		return pairs(themes)
	end
	function VisualThemes:GetTheme(themeName)
		if themeName == L["Default"] then
			return themes[self:GetDefaultTheme()]
		end
		return themes[themeName]
	end
	function VisualThemes:HasTheme(themeName)
		if themeName == L["Default"] then
			return self:HasDefaultTheme()
		elseif themeName == L["Disabled"] then
			return true
		end
		return themes[themeName] ~= nil
	end
end



do
	local Theme
	local themed = {}
	function VisualThemes:OnDisable()
		IsEnabled = false
		for name, theme in self:IterateThemes() do
			theme:OnDisable()
		end
		for frame in pairs(themed) do
			themed[frame] = nil
		end
	end
	local function ThemeChildren(theme, depth, ...)
		for i=1, select("#", ...) do
			Theme(select(i, ...), theme, depth > 1 and depth - 1 or nil)
		end
	end
	local function IsSupported(frame)
		local type = frame:GetObjectType()
		return type ~= "Font" and type ~= "FontInstance"
	end
	local masterIgnoreFrameList = {
		MacroFrame = L["causes tainting issues, thus it won't allow macro changes in combat"],
		SpellBookFrame = L["causes tainting issues, thus it open in combat"],
	}
	local function IsValid(frame, theme)
		if type(frame) ~= "table" or not frame.GetObjectType then
			--VisualThemes:Debug(L["%q cannot be themed, because it's not a frame."], frame ~= nil and frame or "<nil>")
			return false
		end
		if themed[frame] then
			--VisualThemes:Debug(L["%q cannot be themed, because it's already themed."], frame)
			return false
		end
		if frame:IsProtected() then
			--VisualThemes:Debug(L["%q cannot be themed, because it's a protected frame."], frame)
			return false
		end
		local name = frame:GetName() or ""
		if masterIgnoreFrameList[name] then
			--VisualThemes:Debug(L["%q cannot be themed, because: %q."], frame, masterIgnoreFrameList[name])
			return false
		end
		local isCompat, reason = theme:IsCompatible(name)
		if not isCompat then
			--VisualThemes:Debug(L["%q cannot be themed, because %s: %q."], frame, theme.Name, reason)
			return false
		end
		if not IsSupported(frame) then
			--VisualThemes:Debug(L["%q cannot be themed, because it's not a supported type."], frame)
			return false
		end
		return true
	end
	Theme = function(frame, theme, children)
		if IsValid(frame, theme) then
			theme:Theme(frame)

			themed[frame] = theme

			if type(children) == "number" and children > 0 then
				return ThemeChildren(theme, children - 1, frame:GetChildren())
			end
		end
	end
	local GetFrame
	do
		local _G = getfenv(0)
		GetFrame = function(name)
			if type(name) == "string" then
				return _G[name]
			elseif not name then
				return ""
			end
			return name
		end
	end
	function VisualThemes:Theme(frame, theme, childrenDepth)
		frame = GetFrame(frame)
		if type(theme) == "number" and type(childrenDepth) ~= "number" then
			childrenDepth = theme
			theme = nil
		end
		return Theme(frame, self:GetTheme(theme and self:HasTheme(theme) and theme or self:GetDefaultTheme()), childrenDepth)
	end
	local function DethemeChildren(self, depth, ...)
		for i=1, select("#", ...) do
			self:Detheme(select(i, ...), depth > 1 and depth - 1 or nil)
		end
	end
	function VisualThemes:Detheme(frame, depth)
		frame = GetFrame(frame)
		if themed[frame] then
			themed[frame]:Detheme(frame)
			themed[frame] = nil
			if type(depth) == "number" and depth > 0 then
				return DethemeChildren(self, depth - 1, frame:GetChildren())
			end
		end
	end
	function VisualThemes:IsThemed(frame, themeName)
		frame = GetFrame(frame)
		if themeName and self:HasTheme(themeName) then
			return themed[frame] == self:GetTheme(themeName)
		end
		return themed[frame] ~= nil
	end
	function VisualThemes:GetFrameTheme(frame)
		frame = GetFrame(frame)
		return themed[frame]
	end
end

do
	local addonsToTheme, addonDepths, addonFrames = {}, {}, {}
	--[[#EXPLICIT_DOC
	Arguments:
		string - The name of the grouping the frames fall under, such as "Character Frames" or "MyAddon Frames"
		string - The addon's global (folder) name to watch for
		[optional] number - The depth of children that should also be themed, default is 0
		strings|frame objects - The global frame name or reference to a frame object to add to the grouping

	Notes:
		*Use global names if possible.
		*When the addon is loaded (of if the addon is already loaded) the frames will immediately themed
		*AddOns that use dynamic frames shouldn't be themed this way, use this primarily for XML defined frames or frames created on load.

	Example:
		VisualThemes:RegisterAddOn("OneRing", "OneRing", 0, "OneRingFrame")
	]]
	function VisualThemes:RegisterAddOn(group, addon, depth, ...)
		--self:Debug("Registering LoD AddOn for themeing [%q %q %q]", group, addon, depth)
		if type(addon) ~= "string" then return end
		if IsAddOnLoaded(addon) then
			self:RegisterFrames(group, depth, ...)
		elseif not addonsToTheme[addon] and self:IsAddOnLoadable(addon) then
			addonsToTheme[addon] = group
			addonDepths[addon] = depth
			for i=1, select("#", ...) do
				local frame = select(i, ...)
				addonFrames[frame] = addon
			end
		end
	end
	local addonFunc = {}
	--[[#EXPLICIT_DOC
	Arguments:
		string -  The addon's global (folder) name to watch for
		function - A function that is called when the addon is loaded

	Notes:
	*A function is executed when an addon is loaded (or if it's already loaded, immediately) which allows the function to watch for dynamic frames or whatever it needs to do
	*The function is only called a single time, and is passed one argument: the VisualThemes global table.
	*The VisualThemes table can be used to hook or register events.

	Example:
		VisualThemes:RegisterAddOnFunc("Bagnon",
		function(self)
		self:Hook(BagnonFrame, "Create",
			function(obj, ...)
				local frame = self.hooks[obj].Create(obj, ...)
				self:RegisterFrames("Bagnon", 0, frame:GetName())
				return frame
			end)
		end)
	]]
	function VisualThemes:RegisterAddOnFunc(addon, func)
		if type(addon) ~= "string" then return end
		if type(func) ~= "function" then return end
		if not addonFunc[addon] and self:IsAddOnLoadable(addon) then
			if IsAddOnLoaded(addon) then
				func(self)
			else
				addonFunc[addon] = func
			end
		end
	end
	function VisualThemes:ADDON_LOADED(addon)
		if addonsToTheme[addon] then
			for frame in pairs(addonFrames) do
				if addonFrames[frame] == addon then
					self:RegisterFrames(addonsToTheme[addon], addonDepths[addon], frame)
					addonFrames[frame] = nil
				end
			end
			addonsToTheme[addon] = nil
			addonDepths[addon] = nil
		end
		if addonFunc[addon] then
			addonFunc[addon](self)
			addonFunc[addon] = nil
		end
	end
end


function VisualThemes:OnTooltipUpdate()
	GameTooltip:AddLine(L["Visual Themes - %s"]:format(self.version))
	GameTooltip:AddLine(L["|cffffff00Left-Click|r to change settings by the GUI."], 0.2, 1, 0.2)
	GameTooltip:AddLine(L["|cffffff00Right-Click|r to change settings by the dropdown."], 0.2, 1, 0.2)
end